﻿using Engine.Graphics;
using SharpDX;
using System;
using System.Collections.Generic;
using System.Windows.Forms;

namespace Engine.ObjectManager
{
    public class DynObjectManager : IRenderableObject
    {
        #region Private Fields

        private ActionModeEnum actionMode;
        private Engine engine;
        private bool justSelectedANode = false;
        private List<BaseNode> nodes;
        private Vector3 positonWhenNotOnTerrain = Vector3.Zero;
        private int selectedIndex = -1;

        #endregion Private Fields

        #region Public Constructors

        public DynObjectManager(Engine engine)
        {
            this.engine = engine;
            this.nodes = new List<BaseNode>();
        }

        #endregion Public Constructors

        #region Private Destructors

        ~DynObjectManager()
        {
            this.engine.Grafic.RemoveRenderableObject(this);
            this.AddNode = null;
            this.DeleteNode = null;
            this.NodePropertiesChanged = null;
            this.SelectionChanged = null;
        }

        #endregion Private Destructors

        #region Public Delegates

        public delegate void AddNodeHandler(BaseNode node, EventArgs e);

        // Delegates (4) 
        public delegate void DeleteNodeHandler(BaseNode node, EventArgs e);

        public delegate void NodePropertiesHandler(BaseNode node, EventArgs e);

        public delegate void SelectionChangedHandler(BaseNode node, EventArgs e);

        #endregion Public Delegates

        #region Public Events

        /// <summary>
        /// This event gets fired when a new Node is creates.
        /// </summary>
        public event AddNodeHandler AddNode;

        // Events (4) 
        /// <summary>
        /// This events gets fired after a node is during deleting.
        /// First the event gets fired and then the node gets deleted.
        /// </summary>
        public event DeleteNodeHandler DeleteNode;

        /// <summary>
        /// When properties of the Node gets change. eg.: position.
        /// </summary>
        public event NodePropertiesHandler NodePropertiesChanged;

        /// <summary>
        /// When a new node is selected.
        /// </summary>
        public event SelectionChangedHandler SelectionChanged;

        #endregion Public Events

        #region Public Enums

        public enum ActionModeEnum
        {
            NewNode,
            DeleteNode,
            MoveNode
        }

        #endregion Public Enums

        #region Public Properties

        public ActionModeEnum ActionMode
        {
            get { return this.actionMode; }
            set { this.actionMode = value; }
        }

        public List<BaseNode> Nodes
        {
            get { return this.nodes; }
            set { this.nodes = value; }
        }

        public int SelectedIndex
        {
            get { return this.selectedIndex; }
            set
            {
                this.selectedIndex = value;
                if (this.selectedIndex > -1)
                {
                    OnSelectionChange(this.nodes[this.selectedIndex], EventArgs.Empty);
                }
                else
                    OnSelectionChange(null, EventArgs.Empty);
            }
        }

        #endregion Public Properties

        #region Public Methods

        public void ChangeSelectedNode()
        {
            if (this.selectedIndex > -1)
            {
                //this.nodes[selectedIndex].Properties = p;
                if (this.NodePropertiesChanged != null)
                    this.NodePropertiesChanged(this.nodes[this.selectedIndex], null);
            }
        }

        public bool DeleteBaseNode(BaseNode node)
        {
            if (this.nodes.Contains(node))
            {
                if (this.DeleteNode != null)
                    DeleteNode(node, new EventArgs());
                this.nodes.Remove(node);
                this.SelectedIndex = -1;

                return true;
            }
            return false;
        }

        public void Dispose()
        {
            if (this.nodes != null)
                for (int i = 0; i < this.nodes.Count; i++)
                {
                    this.nodes[i].Dispose();
                }
            this.nodes = null;
            this.engine.Grafic.RemoveRenderableObject(this);
        }

        public int GetPriorityLevel()
        {
            return 100;
        }

        public void OnSelectionChange(BaseNode node, EventArgs e)
        {
            if (this.selectedIndex > -1)
            {
                if (this.SelectionChanged != null)
                    this.SelectionChanged(node, e);
            }
        }

        public void Render(int renderStep)
        {
            //for (int i = 0; i < this.nodes.Count; i++)
            //{
            //    this.nodes[i].BoundingBox.Render();
            //}
            if (this.selectedIndex > -1)
                this.nodes[this.selectedIndex].BoundingBox.Render(renderStep);
        }

        public int SelectNode()
        {
            if (this.engine.Mouse == null || !this.engine.Mouse.ButtonL)
                return -1;

            Vector3 intersectionPoint = Vector3.Zero;
            int nearestObject = -1;
            //int intersectedPolygon = -1;
            List<int> cuttingObjects = new List<int>();
            List<Vector3> cuttingPoints = new List<Vector3>();
            //First we check only unlocked nodes.
            for (int i = 0; i < this.nodes.Count; i++)
            {
                if (!this.nodes[i].Locked && (this.nodes[i].BoundingBox != null))
                {
                    bool isObjectsSelected = false;
                    Vector3 cuttingPoint = Vector3.Zero;
                    isObjectsSelected = this.nodes[i].Intersect(this.engine.Grafic.VNear, this.engine.Grafic.VFar, out cuttingPoint, false);
                    if (isObjectsSelected)
                    {
                        cuttingObjects.Add(i);
                        cuttingPoints.Add(cuttingPoint);
                    }
                }
            }

            if (cuttingPoints.Count == 0) // When no object found we search also the locked nodes.
                for (int i = 0; i < this.nodes.Count; i++)
                {
                    if (this.nodes[i].Locked && (this.nodes[i].BoundingBox != null))
                    {
                        bool isObjectsSelected = false;
                        Vector3 cuttingPoint = Vector3.Zero;
                        isObjectsSelected = this.nodes[i].Intersect(this.engine.Grafic.VNear, this.engine.Grafic.VFar, out cuttingPoint, false);
                        if (isObjectsSelected)
                        {
                            cuttingObjects.Add(i);
                            cuttingPoints.Add(cuttingPoint);
                        }
                    }
                }

            // When we have all nodes which are intersecting the ray, we must find the nearest
            float nearestDistance = float.MaxValue;
            for (int i = 0; i < cuttingObjects.Count; i++)
            {
                float distance = GeoMath.DistanceBtw2Points(this.engine.Grafic.VNear, cuttingPoints[i]);
                if (distance < nearestDistance)
                {
                    nearestDistance = distance;
                    nearestObject = cuttingObjects[i];
                    intersectionPoint = cuttingPoints[i];
                }
            }
            //MessageBox.Show(nearestObject.ToString());
            return nearestObject;
        }

        public void UpdateNode(BaseNode baseNode)
        {
            if (baseNode != null)
            {
                if (this.NodePropertiesChanged != null)
                    this.NodePropertiesChanged(baseNode, EventArgs.Empty);
            }
        }

        public void UseThisTool(Vector3 positionIn3dSpace, bool isPositionFromTerrain, bool mouseInRenderwindow, bool allowedToSetNewNodes, NumericUpDown zHeight, Panel renderWindow)
        {
            if (mouseInRenderwindow)
                MoveCamera(isPositionFromTerrain);

            switch (this.actionMode)
            {
                case ActionModeEnum.NewNode:
                    this.selectedIndex = -1;
                    if (mouseInRenderwindow && allowedToSetNewNodes)
                    {
                        if (this.engine.Mouse.ButtonLeftPressingStarted)
                        {
                            BaseNode node = new BaseNode(this.engine, positionIn3dSpace);
                            if (this.AddNode != null)
                                this.AddNode(node, new EventArgs());
                            this.nodes.Add(node);
                        }
                    }
                    break;

                case ActionModeEnum.MoveNode:
                    if (mouseInRenderwindow)
                    {
                        if (this.engine.Mouse.ButtonLeftPressingStarted)
                        {
                            // Select node
                            this.SelectedIndex = SelectNode();
                            this.justSelectedANode = true;
                            if (this.selectedIndex > -1)
                            {
                                this.positonWhenNotOnTerrain = this.nodes[this.selectedIndex].Position;
                                zHeight.Value = (decimal)this.positonWhenNotOnTerrain.Y;
                                Cursor.Position = renderWindow.PointToScreen(this.engine.Grafic.GetScreenCoordinate(this.nodes[this.selectedIndex].Position));
                            }
                        }

                        // This code prevents to move the object when it just is selected.
                        if (this.justSelectedANode && this.engine.Mouse.HasMovedWhilePressingL)
                        {
                            this.justSelectedANode = false;
                        }

                        if (!this.justSelectedANode && this.selectedIndex > -1)
                        {
                            if (!this.nodes[this.selectedIndex].Locked)
                            {
                                if (this.engine.Mouse.ButtonL && positionIn3dSpace != Vector3.Zero)
                                {
                                    // Move node to the new Position
                                    //positionIn3dSpace.Y += this.nodes[this.selectedIndex].HeightOverTerrain;
                                    this.nodes[this.selectedIndex].Position = positionIn3dSpace;
                                    this.positonWhenNotOnTerrain = this.nodes[this.selectedIndex].Position;
                                    if (this.NodePropertiesChanged != null)
                                        this.NodePropertiesChanged(this.nodes[this.selectedIndex], new EventArgs());
                                }
                                if (this.engine.Mouse.ButtonRreleased && !this.engine.Mouse.HasMovedWhilePressingR)
                                {
                                    // Deselecting node
                                    this.SelectedIndex = -1;
                                }

                                if (this.engine.Mouse.ButtonR && !isPositionFromTerrain)
                                {
                                    // Move the height with moving mouse up and down.
                                    zHeight.Value -= (decimal)this.engine.Mouse.AxisY;
                                    //this.nodes[this.selectedIndex].HeightOverTerrain -= this.engine.Mouse.AxisY;
                                    this.positonWhenNotOnTerrain.Y = (float)zHeight.Value;
                                    this.nodes[this.selectedIndex].Position = this.positonWhenNotOnTerrain;

                                    if (this.NodePropertiesChanged != null)
                                        this.NodePropertiesChanged(this.nodes[this.selectedIndex], new EventArgs());
                                }
                            }
                            else if (this.engine.Mouse.HasMovedWhilePressingL)
                                throw new LockedException("Mox Object locked!");
                        }
                    }
                    break;

                case ActionModeEnum.DeleteNode:
                    EventArgs e = new EventArgs();
                    if (this.selectedIndex > -1)
                    {
                        if (this.DeleteNode != null)
                            DeleteNode(this.nodes[this.selectedIndex], e);

                        this.nodes.RemoveAt(this.selectedIndex);
                        this.SelectedIndex = -1;
                    }

                    break;
            }
        }

        #endregion Public Methods

        #region Internal Methods

        internal void LockAllObjects()
        {
            for (int i = 0; i < this.nodes.Count; i++)
            {
                this.nodes[i].Locked = true;
            }
        }

        // Internal Methods (2) 
        internal void UnlockAllObjects()
        {
            for (int i = 0; i < this.nodes.Count; i++)
            {
                this.nodes[i].Locked = false;
            }
        }

        #endregion Internal Methods

        #region Private Methods

        private void MoveCamera(bool getHeightFromTerrain)
        {
            var mouse = this.engine.Mouse;
            var camera = this.engine.Camera;
            NumericUpDown zHeight = new NumericUpDown();

            // Default movings.
            if (mouse.ButtonM)// If middle Mouse-Button is pressed
            {
                camera.RotateTargetAroundCamera(-mouse.AxisX, -mouse.AxisY);
            }
            if (mouse.AxisZ != 0)// ZOOM in  and out
            {
                camera.MoveForward(mouse.AxisZ);
            }

            switch (this.actionMode)
            {
                case ActionModeEnum.NewNode:
                    if (mouse.ButtonRreleased & !mouse.HasMovedWhilePressingR)
                    {
                    }
                    else if (mouse.ButtonR)
                    {
                        if (getHeightFromTerrain | (this.selectedIndex < 0))
                        {
                            camera.MoveUpDown(mouse.AxisY);
                            camera.MoveXY(-mouse.AxisX, 0.0f);
                        }
                        else
                        {
                            zHeight.Value += (decimal)-mouse.AxisY;
                        }
                    }
                    break;

                case ActionModeEnum.MoveNode:
                    if (mouse.ButtonRreleased & !mouse.HasMovedWhilePressingR)
                    {
                    }
                    else if (mouse.ButtonR || mouse.ButtonRreleased)
                    {
                        if (getHeightFromTerrain)
                        {
                            if (mouse.ButtonR)// If right Mouse-Button is pressed
                            {
                                camera.MoveUpDown(mouse.AxisY);
                                camera.MoveXY(-mouse.AxisX, 0.0f);
                            }
                        }
                        else if (!getHeightFromTerrain && this.selectedIndex < 0)
                        {
                            if (mouse.ButtonR)// If right Mouse-Button is pressed
                            {
                                camera.MoveUpDown(mouse.AxisY);
                                camera.MoveXY(-mouse.AxisX, 0.0f);
                            }
                        }
                    }

                    break;

                case ActionModeEnum.DeleteNode:
                    break;
            }
        }

        #endregion Private Methods
    }
}